/** @file matrix.h
 * @brief Este arquivo fonte é distíbuido sob a licença GNU/GPL e contém a classe Matrix e algumas funções auxiliares que modelam operações sobre matrizes.
 *
 *   Este programa é um software livre. Você pode redistribuí-lo e/ou modificá-lo dentro dos termos da Licença Pública Geral GNU como publicada pela Fundação do Software Livre (FSF); na versão 2 da Licença, ou (na sua opinião) qualquer versão posterior.
 */

/*
    Este programa é um software livre; você pode redistribuí-lo e/ou

    modificá-lo dentro dos termos da Licença Pública Geral GNU como

    publicada pela Fundação do Software Livre (FSF); na versão 2 da

    Licença, ou (na sua opinião) qualquer versão.



    Este programa é distribuído na esperança de que possa ser útil,

    mas SEM NENHUMA GARANTIA; sem uma garantia implícita de ADEQUAÇÃO a qualquer

    MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a

    Licença Pública Geral GNU para maiores detalhes.



    Você deve ter recebido uma cópia da Licença Pública Geral GNU

    junto com este programa, se não, escreva para a Fundação do Software

    Livre(FSF) Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#define DEBUG(A) cout << "\n\n\t" # A  "\n\n"

#ifndef MATRIX_H
#define MATRIX_H
#include "rectsupport.h"

/**
 * @brief Modela matrizes com elementos do tipo double.
 *
 * Elementos da matriz são armazenados em um array de _Rows x _Cols do tipo double.
 * \sa RectSupport
 * @author Guilherme Lucio Abelha Mota e Grupo de pesquisa Apoena http://dgp.cnpq.br/buscaoperacional/detalhegrupo.jsp?grupo=0326103T3AKW5D#identificacao
 * @version 1.0
 * @date 11/04/2014
 */
class Matrix : public RectSupport{
private:
    /** @brief Array que armazena os elementos da matriz. */
   double* _Mat; /**!< pointer to double */

protected:
   /**
    * @brief Deleta o array que guarda a matriz, atribui a este NULL e zera os valores de _Rows e _Cols.
    */
   void del();

   /**
    * @brief Aloca uma nova matriz nxm matrix, com elementos iguais a zero.
    *
    * Caso o array atual seja diferente de NULL, o array antigo é desalocado. Os valores de _Rows e _Cols são modificados.
    * @param parRows número de linhas da nova matriz
    * @param parCols número de colunas da nova matriz
    */
   void nw(const unsigned int parRows, const unsigned int parCols);

public:

   /**
    * @brief Construtor da classe Matrix.
    *
    * Cria uma Matrix vazia com o array de elementos _Mat igual a NULL e dimensões 0 x 0.
    */
   Matrix();


   /**
    * @brief Construtor da classe Matrix.
    *
    * Cria uma matriz com \a parRows x \a parCols elementos.
    * @param parRows número de linhas.
    * @param parCols núemro de colunas.
    *
    */
    Matrix(unsigned int parRows, unsigned int parCols);


    /**
     * @brief Construtor da classe Matrix.
     *
     * Cria uma matriz cópia de parMatrix. Para saber as razões para a necessidade deste Construtor veja http://www.doc.ic.ac.uk/lab/cplus/c++.rules/chap7.html

     * @param parMatrix objeto a ser copiado.
     *
     */
    Matrix(const Matrix& parMatrix);

    /**
     * @brief Construtor da classe Matrix.
     *
     * Cria uma matriz com o conteúdo de parMatrix. Para saber as razões para a necessidade deste Construtor veja http://www.cplusplus.com/doc/tutorial/classes2/#move

     * @param parMatrix objeto a ser movido.
     *
     */
    Matrix(Matrix&& parMatrix);


    /**
     * @brief Destrutor da classe Matrix.
     */
    ~Matrix();

    /**
     * @brief Retorna o valor do elemento i,j da Matrix.
     *
     * Caso ele não exista o programa é encerrado.
     * @param i linha
     * @param j coluna
     * @return Matrix [i,j]
     */
    double get(const unsigned int i, const unsigned int j) const;

    /**
     * @brief Concatena horizoltalmente dos objetos da classe Matrix.
     *
     * Ambos objetos devem ter o mesmo número de linhas. Caso contrário o programa é encerrado.
     * @param parMatrix Matrix a ser apensada à matriz corrente.
     * @return objeto da classe Matrix com o resultado.
     */
    Matrix operator |(Matrix& parMatrix);

    /**
     * @brief Concatena horizoltalmente dos objetos da classe Matrix.
     *
     * Ambos objetos devem ter o mesmo número de linhas. Caso contrário o programa é encerrado.
     * @param parMatrix Matrix a ser apensada à matriz corrente.
     * @return objeto da classe Matrix com o resultado.
     */
    Matrix operator |(Matrix&& parMatrix);

    /**
     * @brief Soma duas matrizes.
     *
     * Ambos objetos devem ter as mesmas dimensões. Caso contrário o programa é encerrado.
     * @param parMatrix matriz à diretia do operador + .
     * @return objeto da classe Matrix com o resultado da soma.
     */
    Matrix operator +(Matrix& parMatrix);

    /**
     * @brief Soma duas matrizes.
     *
     * Ambos objetos devem ter as mesmas dimensões. Caso contrário o programa é encerrado.
     * @param parMatrix matriz à diretia do operador + .
     * @return objeto da classe Matrix com o resultado da soma.
     */
    Matrix operator +(Matrix&& parMatrix);

    /**
     * @brief Soma uma matriz com um escalar.
     *
     * @param parScalar escalar a ser somado aos elementos da matrix corrente.
     * @return objeto da classe Matrix com o resultado da soma.
     */
    Matrix operator +(double parScalar);

    /**
     * @brief Subtrai duas matrizes.
     *
     * Ambos objetos devem ter as mesmas dimensões. Caso contrário o programa é encerrado.
     * @param parMatrix matriz à diretia do operador - .
     * @return objeto da classe Matrix com o resultado da subtração.
     */
    Matrix operator -(Matrix& parMatrix);

    /**
     * @brief Subtrai duas matrizes.
     *
     * Ambos objetos devem ter as mesmas dimensões. Caso contrário o programa é encerrado.
     * @param parMatrix matriz à diretia do operador - .
     * @return objeto da classe Matrix com o resultado da subtração.
     */
    Matrix operator -(Matrix&& parMatrix);

    /**
     * @brief Subtrai do objeto Matrix corrente um escalar.
     * @param parScalar escalar à direita do operador - .
     * @return objeto da classe Matrix com o resultado da subtração.
     */
    Matrix operator -(double parScalar);

    /**
     * @brief Multiplica duas matrizes.
     *
     * Ambos objetos devem ter dimensões compatíveis. Caso o número de colunas do objeto Matrix corrente seja diferente do número de linhas de parMatrix, o programa é encerrado.
     * @param parMatrix matriz à diretia do operador * .
     * @return objeto da classe Matrix com o resultado do produto das matrizes.
     */
    Matrix operator *(Matrix& parMatrix);

    /**
     * @brief Multiplica duas matrizes.
     *
     * Ambos objetos devem ter dimensões compatíveis. Caso o número de colunas do objeto Matrix corrente seja diferente do número de linhas de parMatrix, o programa é encerrado.
     * @param parMatrix matriz à diretia do operador * .
     * @return objeto da classe Matrix com o resultado do produto das matrizes.
     */
    Matrix operator *(Matrix&& parMatrix);

    /**
     * @brief Multiplica o objeto Matrix corrente por um escalar.
     * @param parScalar escalar à diretia do operador * .
     * @return objeto da classe Matrix com o resultado do produto.
     */
    Matrix operator *(double parScalar);

    /**
     * This method implements the division of a matrix by a scalar.
     * @param parScalar is the scalar value by which the matrix will be divided.
     * @return The result of the mdivision of the Matrix in the current scope by Par_Scalar.
     */
    Matrix operator /(double parScalar);

    /**
     * @brief Operador de atribuição de matrizes.
     *
     * Muda o conteúdo do objeto Matrix corrente fazendo com que ele se torne uma cópia de parMatrix.
     * @param parMatrix matriz a ser copiada.
     */
    Matrix &operator =(const Matrix& parMatrix);

    /**
     * @brief Operador de atribuição de matrizes.
     *
     * Muda o conteúdo do objeto Matrix corrente fazendo com que ele se torne uma cópia de parMatrix.
     * @param parMatrix matriz a ser copiada.
     */
    Matrix &operator =(const Matrix&& parMatrix);

    /**
     * @brief Muda o conteúdo do elemento (i,j) do objeto Matrix corrente.
     *
     * Caso o as coordenadas (i, j)esteja fora do escopo do objeto corrente o programa é encerrado.
     * @param i linha
     * @param j coluna
     * @param value novo valor relativo ao elemento (i,j) no objeto corrente.
     */
    void put(unsigned int i, unsigned int j, double value) ;

    /**
     * @brief Cria uma cópia de uma parte contínua do objeto Matrix corrente, correspondente ao intervalo [FirstRow, LastRow] x [FirstCol, LastCol]
     *
     * Caso os valores fornecitos entejam fora do escopo do objeto corrente o programa é encerrado.
     * @param FirstRow primeira linha da seleção.
     * @param LastRow  última linha da seleção.
     * @param FirstCol primeira coluna da seleção.
     * @param LastCol  última coluna da seleção.
     * @return objeto Matrix com a cópia do intervalo [FirstRow, LastRow] x [FirstCol, LastCol] do objeto corrente.
     */
    Matrix cut(const unsigned int FirstRow, const unsigned int LastRow, const unsigned int FirstCol, const unsigned int LastCol);

    /**
     * @brief Checa se o objeto Matrix corrente é triangular.
     * @return 1 para matrizes triangulares e 0 caso contrário.
     */
    bool istriang() const;

    /**
     * @brief Imprime o conteúdo da Matrix corrente.
     */
    void show();
};

/** @fn Matrix inverse(Matrix& parMatrix)
 * @brief Calcula a matrix inversa.
 *
 * Usa o método de eliminação de Gauss-Jordan.
 * @param parMatrix matriz a ser invertida.
 * @return a inversa da matriz parMatrix
 */
Matrix inverse(Matrix& parMatrix);

/** @fn Matrix inverse(Matrix&& parMatrix)
 * @brief Calcula a matrix inversa.
 *
 * Usa o método de eliminação de Gauss-Jordan.
 * @param parMatrix matriz a ser invertida.
 * @return a inversa da matriz parMatrix
 */
Matrix inverse(Matrix&& parMatrix);

/** @fn Matrix ones(unsigned int size);
 * @brief Retorna uma matriz quadrada de ordem size com todos os elementos iguais a um.
 * @param size número de linhas e colunas do objeto Matrix retornado.
 */
Matrix ones(unsigned int size);

/** @fn Matrix ones(unsigned int parRows, unsigned int parCols);
 * @brief Retorna uma matriz de dimensões parRows x parCols com todos os elementos iguais a um.
 * @param parRows número de linhas do objeto Matrix retornado.
 * @param parCols número de colunas do objeto Matrix retornado.
 */
Matrix ones(unsigned int parRows, unsigned int parCols);

/** @fn Matrix zeros(unsigned int size);
 * @brief Retorna uma matriz quadrada de ordem size com todos os elementos iguais a zero.
 * @param size número de linhas e colunas do objeto Matrix retornado.
 */
Matrix zeros(unsigned int size);

/** @fn Matrix zeros(unsigned int parRows, unsigned int parCols);
 * @brief Retorna uma matriz de dimensões parRows x parCols com todos os elementos iguais a zero.
 * @param parRows número de linhas do objeto Matrix retornado.
 * @param parCols número de colunas do objeto Matrix retornado.
 */
Matrix zeros(unsigned int parRows, unsigned int parCols);

/** @fn Matrix identity(unsigned int size);
 * @brief Retorna uma matriz identidade de ordem size.
 * @param size número de linhas e colunas do objeto Matrix retornado.
 */
Matrix identity(unsigned int size);

/** @fn Matrix transpose(Matrix& parMatrix)
 * @brief Calcula a matriz transposta.
 * @param parMatrix matriz a ser transposta.
 * @return a transposta da matriz parMatrix
 */
Matrix transpose(Matrix& parMatrix);

/** @fn Matrix transpose(Matrix&& parMatrix)
 * @brief Calcula a matriz transposta.
 * @param matrix matriz a ser transposta.
 * @return a transposta da matriz parMatrix
 */
Matrix transpose(Matrix&& parMatrix);

/** @fn Matrix triang(Matrix& parMatrix)
 * @brief Triangulariza a matrix parMatrix.
 * @param parMatrix matriz a ser triangularizada.
 * @return a transposta da matriz parMatrix
 */
Matrix triang(Matrix& parMatrix);

/** @fn Matrix triang(Matrix&& parMatrix)
 * @brief Triangulariza a matrix parMatrix.
 * @param parMatrix matriz a ser triangularizada.
 * @return a transposta da matriz parMatrix
 */
Matrix triang(Matrix&& parMatrix);

/** @fn Matrix reduction(Matrix& parMatrix)
 * @brief Reduz parMatrix a sua forma canônica.
 * @param parMatrix matriz a ser reduzida.
 * @return a versão reduzida de parMatrix.
 */
Matrix reduction(Matrix& parMatrix);

/** @fn Matrix reduction(Matrix&& parMatrix)
 * @brief Reduz parMatrix a sua forma canônica.
 * @param parMatrix matriz a ser reduzida.
 * @return a versão reduzida de parMatrix.
 */
Matrix reduction(Matrix&& parMatrix);

/** @fn Matrix maxHoriz(Matrix& parMatrix)
 * @brief Retorna uma matriz com o maior valor ao longo de cada uma das linhas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz coluna.
 */
Matrix maxHoriz(Matrix& parMatrix);

/** @fn Matrix maxHoriz(Matrix&& parMatrix)
 * @brief Retorna uma matriz com o maior valor ao longo de cada uma das linhas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz coluna.
 */
Matrix maxHoriz(Matrix&& parMatrix);

/** @fn Matrix maxVert(Matrix& parMatrix)
 * @brief Retorna uma matriz com o maior valor ao longo de cada uma das colunas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz linha.
 */
Matrix maxVert(Matrix& parMatrix);

/** @fn Matrix maxVert(Matrix&& parMatrix)
 * @brief Retorna uma matriz com o maior valor ao longo de cada uma das colunas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz linha.
 */
Matrix maxVert(Matrix&& parMatrix);

/** @fn Matrix max(Matrix& parMatrix)
 * @brief Retorna uma matriz(1,1) com o maior elemento de parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz(1,1) .
 */
Matrix max(Matrix& parMatrix);

/** @fn Matrix max(Matrix&& parMatrix)
 * @brief Retorna uma matriz(1,1) com o maior elemento de parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz(1,1) .
 */
Matrix max(Matrix&& parMatrix);

/** @fn Matrix minHoriz(Matrix& parMatrix)
 * @brief Retorna uma matriz com o menor valor ao longo de cada uma das linhas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz coluna.
 */
Matrix minHoriz(Matrix& parMatrix);

/** @fn Matrix minHoriz(Matrix&& parMatrix)
 * @brief Retorna uma matriz com o menor valor ao longo de cada uma das linhas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz coluna.
 */
Matrix minHoriz(Matrix&& parMatrix);

/** @fn Matrix minVert(Matrix& parMatrix)
 * @brief Retorna uma matriz com o menor valor ao longo de cada uma das colunas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz linha.
 */
Matrix minVert(Matrix& parMatrix);

/** @fn Matrix minVert(Matrix&& parMatrix)
 * @brief Retorna uma matriz com o menor valor ao longo de cada uma das colunas da matriz parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz linha.
 */
Matrix minVert(Matrix&& parMatrix);

/** @fn Matrix max(Matrix& parMatrix)
 * @brief Retorna uma matriz(1,1) com o menor elemento de parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz(1,1) .
 */
Matrix min(Matrix& parMatrix);

/** @fn Matrix max(Matrix&& parMatrix)
 * @brief Retorna uma matriz(1,1) com o menor elemento de parMatrix.
 * @param parMatrix matriz de entrada.
 * @return matriz(1,1) .
 */
Matrix min(Matrix&& parMatrix);

#endif